#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <glib.h>
#include <libsoup/soup.h>

#include "http.h"
#include "jsonrpc.h"
#include "util.h"

static void jsonrpc_method_destroy(gpointer method, gpointer user_data) {
	if (method) {
		jsonrpc_method *m = (jsonrpc_method *) method;
		if (m->name) {
			free(m->name);
			m->name = NULL;
		}

		free(method);
		method = NULL;
	}
}

void jsonrpc_init(jsonrpc_server *server) {
	server->methods = NULL;
}

void jsonrpc_destroy(jsonrpc_server *server) {
	g_list_foreach(server->methods, jsonrpc_method_destroy, NULL );
	g_list_free(server->methods);
}

void jsonrpc_register_method(jsonrpc_server *server, jsonrpc_method_ptr method, const char *name) {
	jsonrpc_method *m = malloc(sizeof(jsonrpc_method));

	m->method = method;
	m->name = strdup(name);

	server->methods = g_list_append(server->methods, m);
}

void jsonrpc_deregister_method(jsonrpc_server *server, const char *name) {
	int size = g_list_length(server->methods);
	int i = 0;

	for (i = 0; i < size; i++) {
		jsonrpc_method *m = (jsonrpc_method *) g_list_nth_data(server->methods, i);
		if (strcmp(name, m->name) == 0) {
			server->methods = g_list_remove(server->methods, m);
			break;
		}
	}
}

static json_object *invoke(jsonrpc_server *server, json_object *obj) {
	json_object *id = json_object_object_get(obj, "id");
	json_object *jsonrpc = json_object_object_get(obj, "jsonrpc");
	const char *method = json_object_get_string(json_object_object_get(obj, "method"));
	json_object *params = json_object_object_get(obj, "params");

	int size = g_list_length(server->methods);
	int i = 0;
	json_object *result = NULL;

	for (i = 0; i < size; i++) {
		jsonrpc_method *m = (jsonrpc_method *) g_list_nth_data(server->methods, i);
		if (strcmp(method, m->name) == 0) {
			json_object *ret = m->method(params);
			if (ret) {
				result = json_object_new_object();
				json_object_object_add(result, "jsonrpc", json_object_get(jsonrpc));
				json_object_object_add(result, "id", json_object_get(id));
				json_object_object_add(result, "result", ret);
			}

			break;
		}
	}

	return result;
}

json_object *jsonrpc_invoke(jsonrpc_server *server, const char *json) {
	json_object *obj = json_tokener_parse(json);
	json_object *result = NULL;

	switch (json_object_get_type(obj)) {
	case json_type_object: {
		result = invoke(server, obj);
		break;
	}

	case json_type_array: {
		int i = 0;
		int size = json_object_array_length(obj);
		result = json_object_new_array();
		for (i = 0; i < size; i++) {
			json_object_array_add(result, invoke(server, json_object_array_get_idx(obj, i)));
		}
		break;
	}

	default:
		break;
	}

	json_object_put(obj);

	return result;
}
